#if defined(__cplusplus) && (__cplusplus >= 201103L) && defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++98-compat"
#endif

#if defined(__cplusplus) && defined(_MSC_VER) && (_MSC_VER < 1920)
#define NOTHROW __declspec(nothrow)
#define NOEXCEPT
#elif defined(__cplusplus) && (__cplusplus >= 201103L)
#define NOTHROW
#define NOEXCEPT noexcept
#else
#define NOTHROW
#define NOEXCEPT
#endif

#if defined(__clang__)
#if __has_warning("-Wunsafe-buffer-usage")
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#endif
#endif

#include <string.h>

MY_TEST_WRAPPER(create) {
  struct hashmap_s hashmap;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(create_zero) {
  struct hashmap_s hashmap;
  ASSERT_EQ(0, hashmap_create(0, &hashmap));
  ASSERT_LT(0u, hashmap_capacity(&hashmap));
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(create_not_power_of_two) {
  struct hashmap_s hashmap;
  ASSERT_EQ(0, hashmap_create(3, &hashmap));
  ASSERT_LE(3u, hashmap_capacity(&hashmap));
  hashmap_destroy(&hashmap);
}

static hashmap_uint32_t custom_hasher(const hashmap_uint32_t seed,
                                      const void *const s,
                                      const hashmap_uint32_t len) {
  hashmap_uint64_t cs = 0;
  memcpy(&cs, &s, sizeof(s));

  return seed ^ HASHMAP_CAST(hashmap_uint32_t, cs >> 32) ^
         HASHMAP_CAST(hashmap_uint32_t, cs) ^ len;
}

static int custom_comparer(const void *const a, const hashmap_uint32_t a_len,
                           const void *const b, const hashmap_uint32_t b_len) {
  return (a_len == b_len) &&
         0 == strncmp(HASHMAP_PTR_CAST(const char *, a),
                      HASHMAP_PTR_CAST(const char *, b), a_len);
}

MY_TEST_WRAPPER(create_ex) {
  int thing = 42;
  const char *const key = "foo";
  struct hashmap_s hashmap;
  struct hashmap_create_options_s options;
  memset(&options, 0, sizeof(options));
  options.initial_capacity = 42;
  options.hasher = &custom_hasher;
  options.comparer = &custom_comparer;

  ASSERT_EQ(0, hashmap_create_ex(options, &hashmap));
  ASSERT_LE(42u, hashmap_capacity(&hashmap));

  ASSERT_EQ(0, hashmap_put(&hashmap, key, HASHMAP_CAST(unsigned, strlen(key)),
                           &thing));

  ASSERT_EQ(&thing,
            HASHMAP_PTR_CAST(const int *,
                             hashmap_get(&hashmap, key,
                                         HASHMAP_CAST(unsigned, strlen(key)))));

  ASSERT_EQ(1u, hashmap_num_entries(&hashmap));

  hashmap_destroy(&hashmap);
}

static int NOTHROW set_context(void *const context,
                               void *const element) NOEXCEPT {
  *HASHMAP_PTR_CAST(int *, context) = *HASHMAP_PTR_CAST(int *, element);
  return 1;
}

MY_TEST_WRAPPER(put) {
  struct hashmap_s hashmap;
  int x = 42;
  int y = 13;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  ASSERT_EQ(0, hashmap_put(&hashmap, "foo",
                           HASHMAP_CAST(unsigned, strlen("foo")), &x));
  ASSERT_EQ(0, hashmap_iterate(&hashmap, set_context, &y));
  ASSERT_EQ(x, y);
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(get_exists) {
  struct hashmap_s hashmap;
  int x = 42;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  ASSERT_EQ(0, hashmap_put(&hashmap, "foo",
                           HASHMAP_CAST(unsigned, strlen("foo")), &x));
  ASSERT_EQ(&x, HASHMAP_PTR_CAST(
                    int *, hashmap_get(&hashmap, "foo",
                                       HASHMAP_CAST(unsigned, strlen("foo")))));
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(get_does_not_exists) {
  struct hashmap_s hashmap;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  ASSERT_FALSE(
      hashmap_get(&hashmap, "foo", HASHMAP_CAST(unsigned, strlen("foo"))));
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(remove) {
  struct hashmap_s hashmap;
  int x = 42;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  ASSERT_EQ(0, hashmap_put(&hashmap, "foo",
                           HASHMAP_CAST(unsigned, strlen("foo")), &x));
  ASSERT_EQ(0, hashmap_remove(&hashmap, "foo",
                              HASHMAP_CAST(unsigned, strlen("foo"))));
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(remove_and_return_key) {
  /* The '&bar' portion of the string just uniques the constant from the 'foo'
   * used later. */
  const char *const key = "foo&bar";

  struct hashmap_s hashmap;
  int x = 42;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  ASSERT_EQ(0, hashmap_put(&hashmap, key, 3, &x));

  /* Use a new string here so that we definitely have a different pointer key
   * being provided. */
  ASSERT_EQ(key, HASHMAP_PTR_CAST(const char *,
                                  hashmap_remove_and_return_key(
                                      &hashmap, "foo",
                                      HASHMAP_CAST(unsigned, strlen("foo")))));
  hashmap_destroy(&hashmap);
}

static int NOTHROW early_exit(void *const context, void *const element) {
  *HASHMAP_PTR_CAST(int *, context) += 1;
  *HASHMAP_PTR_CAST(int *, element) += 1;
  return 0;
}

MY_TEST_WRAPPER(iterate_early_exit) {
  struct hashmap_s hashmap;
  int x[27] = {0};
  int total = 0;
  char s[27];
  char c;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));

  for (c = 'a'; c <= 'z'; c++) {
    s[c - 'a'] = c;
  }

  for (c = 'a'; c <= 'z'; c++) {
    const int index = c - 'a';
    ASSERT_EQ(0, hashmap_put(&hashmap, s + index, 1, x + index));
  }

  ASSERT_EQ(1, hashmap_iterate(&hashmap, early_exit, &total));
  ASSERT_EQ(1, total);

  total = 0;

  for (c = 'a'; c <= 'z'; c++) {
    const int index = c - 'a';
    ASSERT_GE(1, x[index]);
    if (x[index]) {
      total += 1;
    }
  }

  ASSERT_EQ(1, total);

  hashmap_destroy(&hashmap);
}

static int NOTHROW all(void *const context, void *const element) {
  *HASHMAP_PTR_CAST(int *, context) += 1;
  *HASHMAP_PTR_CAST(int *, element) += 1;
  return 1;
}

MY_TEST_WRAPPER(iterate_all) {
  struct hashmap_s hashmap;
  int x[27] = {0};
  int total = 0;
  char s[27];
  char c;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));

  for (c = 'a'; c <= 'z'; c++) {
    s[c - 'a'] = c;
  }

  for (c = 'a'; c <= 'z'; c++) {
    const int index = c - 'a';
    ASSERT_EQ(0, hashmap_put(&hashmap, s + index, 1, x + index));
  }

  ASSERT_EQ(0, hashmap_iterate(&hashmap, all, &total));
  ASSERT_EQ(26, total);

  for (c = 'a'; c <= 'z'; c++) {
    const int index = c - 'a';
    ASSERT_EQ(1, x[index]);
  }

  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(num_entries) {
  struct hashmap_s hashmap;
  int x = 42;
  ASSERT_EQ(0, hashmap_create(1, &hashmap));
  ASSERT_EQ(0u, hashmap_num_entries(&hashmap));
  ASSERT_EQ(0, hashmap_put(&hashmap, "foo",
                           HASHMAP_CAST(unsigned, strlen("foo")), &x));
  ASSERT_EQ(1u, hashmap_num_entries(&hashmap));
  ASSERT_EQ(0, hashmap_remove(&hashmap, "foo",
                              HASHMAP_CAST(unsigned, strlen("foo"))));
  ASSERT_EQ(0u, hashmap_num_entries(&hashmap));
  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(hash_conflict) {
  struct hashmap_s hashmap;

  int x = 42;
  int y = 13;
  int z = -53;

  ASSERT_EQ(0, hashmap_create(4, &hashmap));

  // These all hash to the same value.
  ASSERT_EQ(0, hashmap_put(&hashmap, "000", 3, &x));
  ASSERT_EQ(0, hashmap_put(&hashmap, "002", 3, &y));
  ASSERT_EQ(0, hashmap_put(&hashmap, "007", 3, &z));
  ASSERT_EQ(3u, hashmap_num_entries(&hashmap));

  // Now we remove the middle value.
  ASSERT_EQ(0, hashmap_remove(&hashmap, "002", 3));
  ASSERT_EQ(2u, hashmap_num_entries(&hashmap));

  // And now attempt to insert the last value again. There was a bug where this
  // would insert a new entry incorrectly instead of resolving to the previous
  // entry.
  ASSERT_EQ(0, hashmap_put(&hashmap, "007", 3, &z));
  ASSERT_EQ(2u, hashmap_num_entries(&hashmap));

  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(issue_20) {
  struct hashmap_s hashmap;
  const char *key = "192.168.2.2hv_api.udache.com/abc/def";
  unsigned int len = HASHMAP_CAST(unsigned, strlen(key));

  int value = 42;
  int *ptr = UTEST_NULL;

  hashmap_create(1024, &hashmap);
  hashmap_put(&hashmap, key, len, &value);

  ptr = HASHMAP_PTR_CAST(int *, hashmap_get(&hashmap, key, len));

  ASSERT_EQ(&value, ptr);

  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(one_byte) {
  unsigned char data[256];
  int i;
  struct hashmap_s hashmap;

  for (i = 0; i < 256; i++) {
    data[i] = HASHMAP_CAST(unsigned char, i);
  }

  ASSERT_EQ(0, hashmap_create(1, &hashmap));

  for (i = 0; i < 256; i++) {
    ASSERT_EQ(0, hashmap_put(&hashmap, &data[i], 1, NULL));
  }

  ASSERT_EQ(hashmap_num_entries(&hashmap), 256u);
  ASSERT_LE(hashmap_capacity(&hashmap), 1024u);

  hashmap_destroy(&hashmap);
}

MY_TEST_WRAPPER(two_bytes) {
  unsigned short *data;
  int i;
  struct hashmap_s hashmap;

  data = HASHMAP_PTR_CAST(unsigned short *,
                          malloc(sizeof(unsigned short) * 16384));

  for (i = 0; i < 16384; i++) {
    data[i] = HASHMAP_CAST(unsigned short, i);
  }

  ASSERT_EQ(0, hashmap_create(1, &hashmap));

  for (i = 0; i < 16384; i++) {
    ASSERT_EQ(0, hashmap_put(&hashmap, &data[i], 2, NULL));
  }

  ASSERT_EQ(hashmap_num_entries(&hashmap), 16384u);
  ASSERT_LE(hashmap_capacity(&hashmap), 65536u);

  hashmap_destroy(&hashmap);
  free(data);
}
